﻿#region Using directives

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger;

using Weazel.Gezel.Model;
using Weazel.Gezel.Model.Errors;

using Weazel.Gezel.Parser;

#endregion

namespace BadgerConsoleApplication
{
  class Program
  {
    private static bool useNonDefaultDestination;
		private static bool clockNameSet = false;
		private static bool resetNameSet = false;

    private static List<string> inputFiles = new List<string>();

    /// <summary>
    /// Convert the model 
    /// </summary>
    /// <returns>true iff successful</returns>
    static bool convertModel(Configuration configuration)
    {
      // read the input file
      string filename = inputFiles[0];

      StreamReader reader = null;
      string description = null;

      try
      {
        reader = new StreamReader(filename);
        description = reader.ReadToEnd();
      }
      catch (Exception)
      {
        Console.WriteLine("Error reading file '{0}'", filename);
        return false;
      }
      finally
      {
        if (reader != null)
          reader.Close();
      }

      // Parse
      List<ParseError> parseErrors = new List<ParseError>();
			List<VerificationError> verificationErrors = null;
			List<VerificationWarning> warnings = null;

      Model model = ParseModel.Parse(ref parseErrors, description, filename);

      // print out any errors
      if (parseErrors.Count > 0)
      {
        Console.WriteLine("Error(s): ");
        foreach (ParseError error in parseErrors)
          Console.WriteLine(error.ErrorMessage);

        return false;
      }
      
      // verify the model 
      model.Verify(ref verificationErrors, ref warnings);

      if (verificationErrors.Count > 0)
      {        
        foreach (VerificationError error in verificationErrors)
          Console.WriteLine("Error: " + error.ErrorMessage);

        return false;
      }

			if (warnings.Count > 0)
			{
				for(int i = 0; i < warnings.Count; i++)
					Console.WriteLine("Warning: " + warnings[i].ErrorMessage);			
			}

      // now, carry out the conversion ..
			ConversionContext converter = new ConversionContext(model, configuration);

      converter.Convert();


      return true;
    }

    /// <summary>
    /// Check the validity of a user specified clock or reset name
    /// </summary>
    /// <param name="name">name to check</param>
    /// <returns>true iff ok</returns>
    static bool isValidClockOrResetName(string name)
    {
      bool invalid =
        name.Length == 0 ||
        name[0] == '-' ||
        Char.IsDigit(name[0]) ||
        name.Contains(" ");

      // todo: check for VHDL keywords?

      return !invalid;
    }

		static void setClockNameOption(string[] args, ref int index, ref Configuration configuration)
		{
			if (clockNameSet)
				throw new ArgumentException(String.Format("Clock name already set"));	

			if (index + 1 < args.Length)
			{
				 string name = args[++index];

				if (!isValidClockOrResetName(name))
					throw new ArgumentException(String.Format("Invalid clock name '{0}'", configuration.ClockName));

				configuration.ClockName = name;
				clockNameSet = true;
			}
			else
			{
				throw new ArgumentException(String.Format("'-c' option must be followed by a valid clock name"));		
			}		
		}

		static void setResetNameOption(string[] args, ref int index, ref Configuration configuration)
		{

			if (resetNameSet)
				throw new ArgumentException(String.Format("Reset name already set"));	

			if (index + 1 < args.Length)
			{
				string name = args[++index];

				if (!isValidClockOrResetName(name))
					throw new ArgumentException(String.Format("Invalid reset name '{0}'", configuration.ResetName));

				configuration.ResetName = name;
				resetNameSet = true;
			}
			else
			{
				throw new ArgumentException(String.Format("'-r' option must be followed by a valid reset name"));
			}			
		}

		static void setNonDefaultOutputFolderOption(string[] args, ref int index, ref Configuration configuration)
		{
			// If already set, abort with an error message
			if (useNonDefaultDestination)			
				throw new ArgumentException("Output destination directory already set");						

			// Must be followed by a valid directory name
			if (index + 1 < args.Length)
			{
				configuration.OutputFolder = args[++index];

				try
				{
					if (!System.IO.Directory.Exists(configuration.OutputFolder))
						System.IO.Directory.CreateDirectory(configuration.OutputFolder);
				}
				catch (ArgumentException)
				{
					throw new ArgumentException(String.Format(configuration.OutputFolder + "' is not a valid directory"));					
				}
			}
			else
			{
				throw new ArgumentException("Missing destination directory");				
			}

			useNonDefaultDestination = true;
		}

		static void setSystemOptions(string[] args, ref int index, ref Configuration configuration)
		{
			if (index + 1 < args.Length)
			{
				string system = args[++index];
				configuration.SystemToConvert = system;
			}
			else
			{
				string message =
					"Missing system name in '-s' option";

				throw new ArgumentException(message);
			}
		}

		static void setSynthesisSimulationTargetOption(string[] args, ref int index, ref Configuration configuration)
		{
			if (index + 1 < args.Length)
			{
				string target = args[++index];

				if (target.ToLower() == "simulation")
				{
					// setup 'Configuration' object for simulation
					configuration.ConvertDisplayDirectives = true;
					configuration.CreateXilinxIseProjectFile = false;
					configuration.CreateClockAndResetGenerationProcesses = true;
					configuration.CreatePortsOnTopEntityForUnconnectedNetsInSystem = false;
				}
				else if (target.ToLower() == "synthesis")
				{
					// setup 'Configuration' object for synthesis
					configuration.ConvertDisplayDirectives = false;
					configuration.CreateXilinxIseProjectFile = true;
					configuration.CreateClockAndResetGenerationProcesses = false;
					configuration.CreatePortsOnTopEntityForUnconnectedNetsInSystem = true;
				}
				else
				{
					string message = 
						string.Format("Unknown target '{0}' for '-o' option", target);

					throw new ArgumentException(message);
				}
			}
			else
			{
				string message =
					"Missing 'Simulation' or 'Synthesis' in '-o' option";

				throw new ArgumentException(message);
			}
		}

		static void setReplaceUnconnectedNetsWithPortsOption(string[] args, ref int index, ref Configuration configuration)
		{
			configuration.CreatePortsOnTopEntityForUnconnectedNetsInSystem = true;
		}

		static void setClockAndResetGenerationOption(string[] args, ref int index, ref Configuration configuration)
		{
			configuration.CreateClockAndResetGenerationProcesses = true;
		}

    static Configuration processCommandLineArguments(string[] args)
    {
      Configuration configuration = new Configuration();

      if (args.Length == 0)
        return null;

      int index = 0;

      while (index < args.Length)
      {
        // check for verbose flag
				if (args[index].ToLower() == "-v")
				{
					configuration.Verbose = true;
				}
				// check for clock name flag
				else if (args[index].ToLower() == "-c")
					setClockNameOption(args, ref index, ref configuration);

				// check for reset name flag
				else if (args[index].ToLower() == "-r")
					setResetNameOption(args, ref index, ref configuration);

				// check for destination flag
				else if (args[index].ToLower() == "-d")
					setNonDefaultOutputFolderOption(args, ref index, ref configuration);

				// check for simulation/synthesis target flag
				else if (args[index].ToLower() == "-o")
					setSynthesisSimulationTargetOption(args, ref index, ref configuration);

				// check for "system to convert" option
				else if (args[index].ToLower() == "-s")
					setSystemOptions(args, ref index, ref configuration);

				else if (args[index].ToLower() == "-b")
					setReplaceUnconnectedNetsWithPortsOption(args, ref index, ref configuration);

				else if (args[index].ToLower() == "-clkgen")
					setClockAndResetGenerationOption(args, ref index, ref configuration);

				else if (System.IO.File.Exists(args[index]))
				{
					inputFiles.Add(args[index]);
				}
				else
				{
					throw new ArgumentException(String.Format("Unknown argument '{0}'.", args[index]));
				}

        index++;
      }

      if (inputFiles.Count == 0)
      {
				throw new ArgumentException("Failed: no input files specified");       
      }

      return configuration;
    }

    /// <summary>
    /// prints a message about how to use this program to the 
    /// console.
    /// </summary>
    static void printUsage()
    {
      Console.WriteLine("Usage:\t vhdlconv.exe [-option, ..] input");
			Console.WriteLine();
			Console.WriteLine("Options:");

			Console.WriteLine();
			Console.WriteLine("\t -o synthesis | simulation");
			Console.WriteLine("\t    Use this option to specify if the resulting design should include");
			Console.WriteLine("\t    features available only for simulation ($display directives).");
			Console.WriteLine("\t    Default value: synthesis");

			Console.WriteLine();
      Console.WriteLine("\t -c \"clock name\"");
			Console.WriteLine("\t    Set the name of the clock signal in the resulting VHDL design.");
			Console.WriteLine("\t    Default value: \"clock\"");

			Console.WriteLine();
      Console.WriteLine("\t -r \"reset name\"");
			Console.WriteLine("\t    Set the name of the reset signal in the resulting VHDL design.");
			Console.WriteLine("\t    Default value: \"reset\"");

			Console.WriteLine();
      Console.WriteLine("\t -d \"directory\"");
			Console.WriteLine("\t    Use this option to write resulting VHDL files to a folder");
			Console.WriteLine("\t    of your choosing. If omitted files will be written to the");
			Console.WriteLine("\t    current working directory.");

			Console.WriteLine();
			Console.WriteLine("\t -s \"system\"");
			Console.WriteLine("\t    Use this option to specify which system to convert. Only");
			Console.WriteLine("\t    needed for designs with multiple systems");

			Console.WriteLine();
			Console.WriteLine("\t -b");
			Console.WriteLine("\t    If this option is set the tool will create ports on the top");
			Console.WriteLine("\t    level VHDL entity for all unconnected nets in the system to");
			Console.WriteLine("\t    to be converted.");

			Console.WriteLine();
			Console.WriteLine("\t -clkgen");
			Console.WriteLine("\t    Include clock and reset generation processes. Default is not");
			Console.WriteLine("\t    to include. If '-b' option is set this options will be ignored.");

			Console.WriteLine();
			Console.WriteLine("Example:");
			Console.WriteLine("\t vhdlconv -o synthesis -d c:\\output design.fdl");
			Console.WriteLine();
    }

    static void Main(string[] args)
    {
			Console.WriteLine();
      Console.WriteLine("vhdlconv (codename badger): Gezel to VHDL conversion utillity.");			
      Console.WriteLine();

			Configuration configuration = null;

			try
			{
				configuration = processCommandLineArguments(args);
			}
			catch (ArgumentException e)
			{				
				Console.WriteLine("Argument error: " + e.Message);
				Console.WriteLine();

				printUsage();
			}
			catch (Exception e)
			{
				Console.WriteLine("Unknown error encountered during argument processing:");
				Console.WriteLine(e.GetType().ToString() + ": " );
				Console.WriteLine(e.Message);
				Console.WriteLine(e.StackTrace);
			}

			Console.WriteLine(configuration);
			
      if (configuration == null)
        printUsage();
      else
        convertModel(configuration);			 
    }
  }
}
